/* Copyright (c) 2002,2003,2004 Stefan Haustein, Oberhausen, Rhld., Germany
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The  above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE. */

// Contributors: Bjorn Aadland, Chris Bartley, Nicola Fankhauser,
//               Victor Havin,  Christian Kurzke, Bogdan Onoiu,
//                Elias Ross, Jain Sanjay, David Santoro.

package org.kxml2.wap;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.*;
import java.util.Hashtable;
import java.util.Vector;


public class WbxmlParser implements XmlPullParser {

    /**
     */
    String [] TYPES = {
        "START_DOCUMENT",
            "END_DOCUMENT",
            "START_TAG",
            "END_TAG",
            "TEXT",
            "CDSECT",
            "ENTITY_REF",
            "IGNORABLE_WHITESPACE",
            "PROCESSING_INSTRUCTION",
            "COMMENT",
            "DOCDECL"
    };

    static final String HEX_DIGITS = "0123456789abcdef";

    /**
     * Parser event type for Wbxml-specific events. The Wbxml event code can be
     * accessed with getWapCode()
     */

    public static final int WAP_EXTENSION = 64;

    static final private String UNEXPECTED_EOF =
            "Unexpected EOF";
    static final private String ILLEGAL_TYPE =
            "Wrong event type";

    private InputStream in;

    private int TAG_TABLE = 0;
    private int ATTR_START_TABLE = 1;
    private int ATTR_VALUE_TABLE = 2;

    private String[] attrStartTable;
    private String[] attrValueTable;
    private String[] tagTable;
    private byte[] stringTable;
    private Hashtable cacheStringTable = null;
    private boolean processNsp;

    private int depth;
    private String[] elementStack = new String[16];
    private String[] nspStack = new String[8];
    private int[] nspCounts = new int[4];

    private int attributeCount;
    private String[] attributes = new String[16];
    private int nextId = -2;

    private Vector tables = new Vector();

    //    StartTag current;
    //    ParseEvent next;

    private String prefix;
    private String namespace;
    private String name;
    private String text;

    private Object wapExtensionData;
    private int wapCode;

    private int type;

    private boolean degenerated;
    private boolean isWhitespace;
    private String encoding;

    public WbxmlParser() {
    }

    public boolean getFeature(String feature) {
        return XmlPullParser.FEATURE_PROCESS_NAMESPACES.equals(feature) && processNsp;
    }

    public String getInputEncoding() {
        return encoding;
    }

    public void defineEntityReplacementText(
            String entity,
            String value)
            throws XmlPullParserException {

        // just ignore, has no effect
    }

    public Object getProperty(String property) {
        return null;
    }

    public int getNamespaceCount(int depth) {
        if (depth > this.depth)
            throw new IndexOutOfBoundsException();
        return nspCounts[depth];
    }

    public String getNamespacePrefix(int pos) {
        return nspStack[pos << 1];
    }

    public String getNamespaceUri(int pos) {
        return nspStack[(pos << 1) + 1];
    }

    public String getNamespace(String prefix) {

        if ("xml".equals(prefix))
            return "http://www.w3.org/XML/1998/namespace";
        if ("xmlns".equals(prefix))
            return "http://www.w3.org/2000/xmlns/";

        for (int i = (getNamespaceCount(depth) << 1) - 2;
             i >= 0;
             i -= 2) {
            if (prefix == null) {
                if (nspStack[i] == null)
                    return nspStack[i + 1];
            } else if (prefix.equals(nspStack[i]))
                return nspStack[i + 1];
        }
        return null;
    }

    public int getDepth() {
        return depth;
    }

    public String getPositionDescription() {

        StringBuffer buf =
                new StringBuffer(
                        type < TYPES.length ? TYPES[type] : "unknown");
        buf.append(' ');

        if (type == START_TAG || type == END_TAG) {
            if (degenerated)
                buf.append("(empty) ");
            buf.append('<');
            if (type == END_TAG)
                buf.append('/');

            if (prefix != null)
                buf.append('{').append(namespace).append('}').append(prefix).append(':');
            buf.append(name);

            int cnt = attributeCount << 2;
            for (int i = 0; i < cnt; i += 4) {
                buf.append(' ');
                if (attributes[i + 1] != null)
                    buf.append('{').append(attributes[i]).append('}').append(attributes[i
                            + 1]).append(':');
                buf.append(attributes[i + 2]).append("='").append(attributes[i + 3]).append('\'');
            }

            buf.append('>');
        } else if (type == IGNORABLE_WHITESPACE) ;
        else if (type != TEXT)
            buf.append(getText());
        else if (isWhitespace)
            buf.append("(whitespace)");
        else {
            String text = getText();
            if (text.length() > 16)
                text = text.substring(0, 16) + "...";
            buf.append(text);
        }

        return buf.toString();
    }

    public int getLineNumber() {
        return -1;
    }

    public int getColumnNumber() {
        return -1;
    }

    public boolean isWhitespace()
            throws XmlPullParserException {
        if (type != TEXT
                && type != IGNORABLE_WHITESPACE
                && type != CDSECT)
            exception(ILLEGAL_TYPE);
        return isWhitespace;
    }

    public String getText() {
        return text;
    }

    public char[] getTextCharacters(int[] poslen) {
        if (type >= TEXT) {
            poslen[0] = 0;
            poslen[1] = text.length();
            char[] buf = new char[text.length()];
            text.getChars(0, text.length(), buf, 0);
            return buf;
        }

        poslen[0] = -1;
        poslen[1] = -1;
        return null;
    }

    public String getNamespace() {
        return namespace;
    }

    public String getName() {
        return name;
    }

    public String getPrefix() {
        return prefix;
    }

    public boolean isEmptyElementTag()
            throws XmlPullParserException {
        if (type != START_TAG)
            exception(ILLEGAL_TYPE);
        return degenerated;
    }

    public int getAttributeCount() {
        return attributeCount;
    }

    public String getAttributeType(int index) {
        return "CDATA";
    }

    public boolean isAttributeDefault(int index) {
        return false;
    }

    public String getAttributeNamespace(int index) {
        if (index >= attributeCount)
            throw new IndexOutOfBoundsException();
        return attributes[index << 2];
    }

    public String getAttributeName(int index) {
        if (index >= attributeCount)
            throw new IndexOutOfBoundsException();
        return attributes[(index << 2) + 2];
    }

    public String getAttributePrefix(int index) {
        if (index >= attributeCount)
            throw new IndexOutOfBoundsException();
        return attributes[(index << 2) + 1];
    }

    public String getAttributeValue(int index) {
        if (index >= attributeCount)
            throw new IndexOutOfBoundsException();
        return attributes[(index << 2) + 3];
    }

    public String getAttributeValue(
            String namespace,
            String name) {

        for (int i = (attributeCount << 2) - 4;
             i >= 0;
             i -= 4) {
            if (attributes[i + 2].equals(name)
                    && (namespace == null
                    || attributes[i].equals(namespace)))
                return attributes[i + 3];
        }

        return null;
    }

    public int getEventType() throws XmlPullParserException {
        return type;
    }

    // TODO: Reuse resolveWapExtension here? Raw Wap extensions would still be accessible
    // via nextToken();  ....?

    public int next() throws XmlPullParserException, IOException {

        isWhitespace = true;
        int minType = 9999;

        while (true) {

            String save = text;

            nextImpl();

            if (type < minType)
                minType = type;

            if (minType > CDSECT) continue; // no "real" event so far

            if (minType >= TEXT) {  // text, see if accumulate

                if (save != null) text = text == null ? save : save + text;

                switch (peekId()) {
                    case Wbxml.ENTITY:
                    case Wbxml.STR_I:
                    case Wbxml.STR_T:
                    case Wbxml.LITERAL:
                    case Wbxml.LITERAL_C:
                    case Wbxml.LITERAL_A:
                    case Wbxml.LITERAL_AC:
                        continue;
                }
            }

            break;
        }

        type = minType;

        if (type > TEXT)
            type = TEXT;

        return type;
    }


    public int nextToken() throws XmlPullParserException, IOException {

        isWhitespace = true;
        nextImpl();
        return type;
    }


    public int nextTag() throws XmlPullParserException, IOException {

        next();
        if (type == TEXT && isWhitespace)
            next();

        if (type != END_TAG && type != START_TAG)
            exception("unexpected type");

        return type;
    }


    public String nextText() throws XmlPullParserException, IOException {
        if (type != START_TAG)
            exception("precondition: START_TAG");

        next();

        String result;

        if (type == TEXT) {
            result = getText();
            next();
        } else
            result = "";

        if (type != END_TAG)
            exception("END_TAG expected");

        return result;
    }


    public void require(int type, String namespace, String name)
            throws XmlPullParserException, IOException {

        if (type != this.type
                || (namespace != null && !namespace.equals(getNamespace()))
                || (name != null && !name.equals(getName())))
            exception(
                    "expected: " + (type == WAP_EXTENSION ? "WAP Ext." : (TYPES[type] + " {" + namespace + '}' + name)));
    }


    public void setInput(Reader reader) throws XmlPullParserException {
        exception("InputStream required");
    }

    public void setInput(InputStream in, String enc)
            throws XmlPullParserException {

        this.in = in;

        try {
            int version=readByte();
            int publicIdentifierId=readInt();

            if (publicIdentifierId == 0)
                readInt();

            int charset = readInt(); // skip charset

            if (null == enc) {
                switch (charset) {
                    case 4:
                        encoding = "ISO-8859-1";
                        break;
                    case 106:
                        encoding = "UTF-8";
                        break;
                        // add more if you need them
                        // http://www.iana.org/assignments/character-sets
                        // case MIBenum: encoding = Name  break;
                    default:
                        throw new UnsupportedEncodingException("" + charset);
                }
            } else {
                encoding = enc;
            }

            int strTabSize = readInt();
            stringTable = new byte[strTabSize];

            int ok = 0;
            while (ok < strTabSize) {
                int cnt = in.read(stringTable, ok, strTabSize - ok);
                if (cnt <= 0) break;
                ok += cnt;
            }

            selectPage(0, true);
            selectPage(0, false);
        }
        catch (IOException e) {
            exception("Illegal input format");
        }
    }

    public void setFeature(String feature, boolean value)
            throws XmlPullParserException {
        if (XmlPullParser.FEATURE_PROCESS_NAMESPACES.equals(feature))
            processNsp = value;
        else
            exception("unsupported feature: " + feature);
    }

    public void setProperty(String property, Object value)
            throws XmlPullParserException {
        throw new XmlPullParserException("unsupported property: " + property);
    }

    // ---------------------- private / internal methods

    private boolean adjustNsp()
            throws XmlPullParserException {

        boolean any = false;

        for (int i = 0; i < attributeCount << 2; i += 4) {
            // * 4 - 4; i >= 0; i -= 4) {

            String attrName = attributes[i + 2];
            int cut = attrName.indexOf(':');
            String prefix;

            if (cut != -1) {
                prefix = attrName.substring(0, cut);
                attrName = attrName.substring(cut + 1);
            } else if ("xmlns".equals(attrName)) {
                prefix = attrName;
                attrName = null;
            } else
                continue;

            if (!"xmlns".equals(prefix)) {
                any = true;
            } else {
                int j = (nspCounts[depth]++) << 1;

                nspStack = ensureCapacity(nspStack, j + 2);
                nspStack[j] = attrName;
                nspStack[j + 1] = attributes[i + 3];

                if (attrName != null
                        && attributes[i + 3].length() == 0)
                    exception("illegal empty namespace");

                //  prefixMap = new PrefixMap (prefixMap, attrName, attr.getValue ());

                ////    System.out.println (prefixMap);

                System.arraycopy(
                        attributes,
                        i + 4,
                        attributes,
                        i,
                        ((--attributeCount) << 2) - i);

                i -= 4;
            }
        }

        if (any) {
            for (int i = (attributeCount << 2) - 4;
                 i >= 0;
                 i -= 4) {

                String attrName = attributes[i + 2];
                int cut = attrName.indexOf(':');

                if (cut == 0)
                    throw new RuntimeException(
                            "illegal attribute name: "
                                    + attrName
                                    + " at "
                                    + this);

                else if (cut != -1) {
                    String attrPrefix =
                            attrName.substring(0, cut);

                    attrName = attrName.substring(cut + 1);

                    String attrNs = getNamespace(attrPrefix);

                    if (attrNs == null)
                        throw new RuntimeException(
                                "Undefined Prefix: "
                                        + attrPrefix
                                        + " in "
                                        + this);

                    attributes[i] = attrNs;
                    attributes[i + 1] = attrPrefix;
                    attributes[i + 2] = attrName;

                    for (int j = (attributeCount << 2) - 4;
                         j > i;
                         j -= 4)
                        if (attrName.equals(attributes[j + 2])
                                && attrNs.equals(attributes[j]))
                            exception(
                                    "Duplicate Attribute: {"
                                            + attrNs
                                            + '}'
                                            + attrName);
                }
            }
        }

        int cut = name.indexOf(':');

        if (cut == 0)
            exception("illegal tag name: " + name);
        else if (cut != -1) {
            prefix = name.substring(0, cut);
            name = name.substring(cut + 1);
        }

        this.namespace = getNamespace(prefix);

        if (this.namespace == null) {
            if (prefix != null)
                exception("undefined prefix: " + prefix);
            this.namespace = NO_NAMESPACE;
        }

        return any;
    }

    private void setTable(int page, int type, String[] table) {
        if (stringTable != null) {
            throw new RuntimeException("setXxxTable must be called before setInput!");
        }
        while (tables.size() < 3 * page + 3) {
            tables.addElement(null);
        }
        tables.setElementAt(table, page * 3 + type);
    }


    private void exception(String desc)
            throws XmlPullParserException {
        throw new XmlPullParserException(desc, this, null);
    }


    private void selectPage(int nr, boolean tags) throws XmlPullParserException {
        if (tables.isEmpty() && nr == 0) return;

        if (nr * 3 > tables.size())
            exception("Code Page " + nr + " undefined!");

        if (tags)
            tagTable = (String[]) tables.elementAt(nr * 3 + TAG_TABLE);
        else {
            attrStartTable = (String[]) tables.elementAt(nr * 3 + ATTR_START_TABLE);
            attrValueTable = (String[]) tables.elementAt(nr * 3 + ATTR_VALUE_TABLE);
        }
    }

    private void nextImpl()
            throws IOException, XmlPullParserException {

        String s;

        if (type == END_TAG) {
            depth--;
        }

        if (degenerated) {
            type = XmlPullParser.END_TAG;
            degenerated = false;
            return;
        }

        text = null;
        prefix = null;
        name = null;

        int id = peekId();
        while (id == Wbxml.SWITCH_PAGE) {
            nextId = -2;
            selectPage(readByte(), true);
            id = peekId();
        }
        nextId = -2;

        switch (id) {
            case-1:
                type = XmlPullParser.END_DOCUMENT;
                break;

            case Wbxml.END: {
                int sp = (depth - 1) << 2;

                type = END_TAG;
                namespace = elementStack[sp];
                prefix = elementStack[sp + 1];
                name = elementStack[sp + 2];
            }
            break;

            case Wbxml.ENTITY: {
                type = ENTITY_REF;
                char c = (char) readInt();
                text = "" + c;
                name = "#" + ((int) c);
            }

            break;

            case Wbxml.STR_I:
                type = TEXT;
                text = readStrI();
                break;

            case Wbxml.EXT_I_0:
            case Wbxml.EXT_I_1:
            case Wbxml.EXT_I_2:
            case Wbxml.EXT_T_0:
            case Wbxml.EXT_T_1:
            case Wbxml.EXT_T_2:
            case Wbxml.EXT_0:
            case Wbxml.EXT_1:
            case Wbxml.EXT_2:
            case Wbxml.OPAQUE:

                type = WAP_EXTENSION;
                wapCode = id;
                wapExtensionData = parseWapExtension(id);
                break;

            case Wbxml.PI:
                throw new RuntimeException("PI curr. not supp.");
                // readPI;
                // break;

            case Wbxml.STR_T: {
                type = TEXT;
                text = readStrT();
            }
            break;

            default:
                parseElement(id);
        }
        //        }
        //      while (next == null);

        //        return next;
    }

    /**
     * Overwrite this method to intercept all wap events
     */

    public Object parseWapExtension(int id) throws IOException, XmlPullParserException {

        switch (id) {
            case Wbxml.EXT_I_0:
            case Wbxml.EXT_I_1:
            case Wbxml.EXT_I_2:
                return readStrI();

            case Wbxml.EXT_T_0:
            case Wbxml.EXT_T_1:
            case Wbxml.EXT_T_2:
                return new Integer(readInt());

            case Wbxml.EXT_0:
            case Wbxml.EXT_1:
            case Wbxml.EXT_2:
                return null;

            case Wbxml.OPAQUE: {
                int count = readInt();
                byte[] buf = new byte[count];

                while (count > 0) {
                    count -= in.read(buf, buf.length - count, count);
                }

                return buf;
            } // case OPAQUE


            default:
                exception("illegal id: " + id);
                return null; // dead code
        } // SWITCH
    }

    public void readAttr() throws IOException, XmlPullParserException {

        int id = readByte();
        int i = 0;

        while (id != 1) {

            while (id == Wbxml.SWITCH_PAGE) {
                selectPage(readByte(), false);
                id = readByte();
            }

            String name = resolveId(attrStartTable, id);
            StringBuffer value;

            int cut = name.indexOf('=');

            if (cut == -1)
                value = new StringBuffer();
            else {
                value =
                        new StringBuffer(name.substring(cut + 1));
                name = name.substring(0, cut);
            }

            id = readByte();
            while (id > 128
                    || id == Wbxml.SWITCH_PAGE
                    || id == Wbxml.ENTITY
                    || id == Wbxml.STR_I
                    || id == Wbxml.STR_T
                    || (id >= Wbxml.EXT_I_0 && id <= Wbxml.EXT_I_2)
                    || (id >= Wbxml.EXT_T_0 && id <= Wbxml.EXT_T_2)) {

                switch (id) {
                    case Wbxml.SWITCH_PAGE:
                        selectPage(readByte(), false);
                        break;

                    case Wbxml.ENTITY:
                        value.append((char) readInt());
                        break;

                    case Wbxml.STR_I:
                        value.append(readStrI());
                        break;

                    case Wbxml.EXT_I_0:
                    case Wbxml.EXT_I_1:
                    case Wbxml.EXT_I_2:
                    case Wbxml.EXT_T_0:
                    case Wbxml.EXT_T_1:
                    case Wbxml.EXT_T_2:
                    case Wbxml.EXT_0:
                    case Wbxml.EXT_1:
                    case Wbxml.EXT_2:
                    case Wbxml.OPAQUE:
                        value.append(resolveWapExtension(id, parseWapExtension(id)));
                        break;

                    case Wbxml.STR_T:
                        value.append(readStrT());
                        break;

                    default:
                        value.append(
                                resolveId(attrValueTable, id));
                }

                id = readByte();
            }

            attributes = ensureCapacity(attributes, i + 4);

            attributes[i++] = "";
            attributes[i++] = null;
            attributes[i++] = name;
            attributes[i++] = value.toString();

            attributeCount++;
        }
    }

    private int peekId() throws IOException {
        if (nextId == -2) {
            nextId = in.read();
        }
        return nextId;
    }

    /**
     * overwrite for own WAP extension handling in attributes and high level parsing
     * (above nextToken() level)
     */

    protected String resolveWapExtension(int id, Object data) {

        if (data instanceof byte[]) {
            StringBuffer sb = new StringBuffer();
            byte[] b = (byte[]) data;

            for (int i = 0; i < b.length; i++) {
                sb.append(HEX_DIGITS.charAt((b[i] >> 4) & 0x0f));
                sb.append(HEX_DIGITS.charAt(b[i] & 0x0f));
            }
            return sb.toString();
        }

        return "$(" + data + ')';
    }

    String resolveId(String[] tab, int id) throws IOException {
        int idx = (id & 0x07f) - 5;
        if (idx == -1) {
            wapCode = -1;
            return readStrT();
        }
        if (idx < 0
                || tab == null
                || idx >= tab.length
                || tab[idx] == null)
            throw new IOException("id " + id + " undef.");

        wapCode = idx + 5;

        return tab[idx];
    }

    void parseElement(int id)
            throws IOException, XmlPullParserException {

        type = START_TAG;
        name = resolveId(tagTable, id & 0x03f);

        attributeCount = 0;
        if ((id & 128) != 0) {
            readAttr();
        }

        degenerated = (id & 64) == 0;

        int sp = depth++ << 2;

        // transfer to element stack

        elementStack = ensureCapacity(elementStack, sp + 4);
        elementStack[sp + 3] = name;

        if (depth >= nspCounts.length) {
            int[] bigger = new int[depth + 4];
            System.arraycopy(nspCounts, 0, bigger, 0, nspCounts.length);
            nspCounts = bigger;
        }

        nspCounts[depth] = nspCounts[depth - 1];

        for (int i = attributeCount - 1; i > 0; i--) {
            for (int j = 0; j < i; j++) {
                if (getAttributeName(i)
                        .equals(getAttributeName(j)))
                    exception(
                            "Duplicate Attribute: "
                                    + getAttributeName(i));
            }
        }

        if (processNsp)
            adjustNsp();
        else
            namespace = "";

        elementStack[sp] = namespace;
        elementStack[sp + 1] = prefix;
        elementStack[sp + 2] = name;

    }

    private String[] ensureCapacity(
            String[] arr,
            int required) {
        if (arr.length >= required)
            return arr;
        String[] bigger = new String[required + 16];
        System.arraycopy(arr, 0, bigger, 0, arr.length);
        return bigger;
    }

    int readByte() throws IOException {
        int i = in.read();
        if (i == -1)
            throw new IOException("Unexpected EOF");
        return i;
    }

    int readInt() throws IOException {
        int result = 0;
        int i;

        do {
            i = readByte();
            result = (result << 7) | (i & 0x7f);
        }
        while ((i & 0x80) != 0);

        return result;
    }

    String readStrI() throws IOException {
        ByteArrayOutputStream buf = new ByteArrayOutputStream();
        boolean wsp = true;
        while (true) {
            int i = in.read();
            if (i == 0) {
                break;
            }
            if (i == -1) {
                throw new IOException(UNEXPECTED_EOF);
            }
            if (i > 32) {
                wsp = false;
            }
            buf.write(i);
        }
        isWhitespace = wsp;
        String result = new String(buf.toByteArray(), encoding);
        buf.close();
        return result;
    }

    String readStrT() throws IOException {
        int pos = readInt();
        // As the main reason of stringTable is compression we build a cache of Strings
        // stringTable is supposed to help create Strings from parts which means some cache hit rate
        // This will help to minimize the Strings created when invoking readStrT() repeatedly
        if (cacheStringTable == null) {
            //Lazy init if device is not using StringTable but inline 0x03 strings
            cacheStringTable = new Hashtable();
        }
        String forReturn = (String) cacheStringTable.get(new Integer(pos));
        if (forReturn == null) {

            int end = pos;
            while (end < stringTable.length && stringTable[end] != '\0') {
                end++;
            }
            forReturn = new String(stringTable, pos, end - pos, encoding);
            cacheStringTable.put(new Integer(pos), forReturn);
        }
        return forReturn;
    }

    /**
     * Sets the tag table for a given page.
     * The first string in the array defines tag 5, the second tag 6 etc.
     */

    public void setTagTable(int page, String[] table) {
        setTable(page, TAG_TABLE, table);

        //        this.tagTable = tagTable;
        //      if (page != 0)
        //        throw new RuntimeException("code pages curr. not supp.");
    }

    /**
     * Sets the attribute start Table for a given page.
     * The first string in the array defines attribute
     * 5, the second attribute 6 etc. Please use the
     * character '=' (without quote!) as delimiter
     * between the attribute name and the (start of the) value
     */

    public void setAttrStartTable(
            int page,
            String[] table) {

        setTable(page, ATTR_START_TABLE, table);
    }

    /**
     * Sets the attribute value Table for a given page.
     * The first string in the array defines attribute value 0x85,
     * the second attribute value 0x86 etc.
     */

    public void setAttrValueTable(
            int page,
            String[] table) {

        setTable(page, ATTR_VALUE_TABLE, table);
    }

    /**
     * Returns the token ID for start tags or the event type for wap proprietary events
     * such as OPAQUE.
     */

    public int getWapCode() {
        return wapCode;
    }

    public Object getWapExtensionData() {
        return wapExtensionData;
    }


}